//
// Copyright (c) 2011 Alex Nesterenko
//

#include "nane/RenderSystem/ogl/OGLBufferManager.h"

#include <cstring>
#include <vector>

namespace nane
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
    static const GLenum s_toGLPrimitiveMode[EPrimitiveType::size] =
    {
          GL_POINTS         // PointList
        , GL_LINES          // LineList
        , GL_LINE_STRIP     // LineStrip
        , GL_TRIANGLES      // TriangleList
        , GL_TRIANGLE_STRIP // TriangleStrip
        , GL_TRIANGLE_FAN   // TriangleFan
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////
    static uint32 s_getIndiciesCountFromPrimitive(EPrimitiveType _type, uint32 _primitiveCount)
    {
        switch(_type.raw_value())
        {
            case EPrimitiveType::PointList:
                return _primitiveCount;
            case EPrimitiveType::LineList:
                return _primitiveCount * 2;
            case EPrimitiveType::LineStrip:
                return _primitiveCount + 1;
            case EPrimitiveType::TriangleList:
                return _primitiveCount * 3;
            case EPrimitiveType::TriangleStrip:
            case EPrimitiveType::TriangleFan:
                return _primitiveCount + 2;
        }
        return 0;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    struct OGLVertexBuffer
        : public IVertexBuffer
    {
        GLuint bufferID;
        EResourceUsage usage;
        uint32 verticesNum;
        uint32 vertexSize;
        std::vector<VertexElement> vertexDeclaration;

        OGLVertexBuffer( GLuint _bufferID, EResourceUsage _usage, uint32 _verticesNum, uint32 _vertexSize )
            : bufferID( _bufferID )
            , usage( _usage )
            , verticesNum( _verticesNum )
            , vertexSize( _vertexSize )
        {

        }

    };
    ////////////////////////////////////////////////////////////////////////////////////////////////
    struct OGLIndexBuffer
        : public IIndexBuffer
    {
        GLuint bufferID;
        EResourceUsage usage;
        uint32 indiciesNum;

        OGLIndexBuffer( GLuint _bufferID, EResourceUsage _usage, uint32 _indiciesNum )
            : bufferID( _bufferID )
            , usage( _usage )
            , indiciesNum( _indiciesNum )
        {

        }
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////
    OGLBufferManager::OGLBufferManager()
        : m_currentVertexBuffer( NULL )
        , m_currentIndexBuffer( NULL )
        , m_activeVertexBuffer( 0 )
        , m_activeIndexBuffer( 0 )
    {

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    OGLBufferManager::~OGLBufferManager()
    {

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    IVertexBuffer* OGLBufferManager::createVertexBuffer( EResourceUsage _usage, uint32 _verticesNum, uint32 _vertexSize
                                                       , VertexElement* _elements, uint32 _elementsNum, byte* _initData )
    {
        OGLVertexBuffer* oglVertexBuffer = NULL;
        GLuint bufferID = 0;
        OGLAPI::glGenBuffers(1, &bufferID);
        if( bufferID != 0 )
        {
            setActiveVertexBuffer_(bufferID);
            OGLAPI::glBufferData(GL_ARRAY_BUFFER, _verticesNum * _vertexSize, _initData, GL_STATIC_DRAW);
            oglVertexBuffer = new OGLVertexBuffer(bufferID, _usage, _verticesNum, _vertexSize);
            oglVertexBuffer->vertexDeclaration.resize(_elementsNum);
            memcpy(&(oglVertexBuffer->vertexDeclaration[0]), _elements, _elementsNum * sizeof(VertexElement));
        }
        return oglVertexBuffer;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLBufferManager::releaseVertexBuffer( IVertexBuffer* _vertexBuffer )
    {
        if( m_currentVertexBuffer == _vertexBuffer )
        {
            setVertexBuffer(0, NULL);
        }

        OGLVertexBuffer* oglVertexBuffer = static_cast<OGLVertexBuffer*>(_vertexBuffer);
        if( oglVertexBuffer != NULL )
        {
            OGLAPI::glDeleteBuffers(1, &oglVertexBuffer->bufferID);
            delete oglVertexBuffer;
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    byte* OGLBufferManager::lockVertexBuffer( IVertexBuffer* _vertexBuffer, uint32 _offset, uint32 _size )
    {
        byte* lockedMemory = NULL;
        OGLVertexBuffer* oglVertexBuffer = static_cast<OGLVertexBuffer*>(_vertexBuffer);
        if( oglVertexBuffer != NULL )
        {
            setActiveVertexBuffer_(oglVertexBuffer->bufferID);
            if( oglVertexBuffer->usage == EResourceUsage::Default )
            {
                lockedMemory = static_cast<byte*>(OGLAPI::glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY));
            }
        }
        return lockedMemory;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLBufferManager::unlockVertexBuffer( IVertexBuffer* _vertexBuffer )
    {
        OGLVertexBuffer* oglVertexBuffer = static_cast<OGLVertexBuffer*>(_vertexBuffer);
        if( oglVertexBuffer != NULL )
        {
            setActiveVertexBuffer_(oglVertexBuffer->bufferID);
            OGLAPI::glUnmapBuffer(GL_ARRAY_BUFFER);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLBufferManager::setVertexBuffer( uint16 _streamNumber, IVertexBuffer* _vertexBuffer )
    {
        m_currentVertexBuffer = _vertexBuffer;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    IIndexBuffer* OGLBufferManager::createIndexBuffer( EResourceUsage _usage, uint32 _indiciesNum, uint16* _initData )
    {

        OGLIndexBuffer* oglIndexBuffer = NULL;
        GLuint bufferID = 0;
        OGLAPI::glGenBuffers(1, &bufferID);
        if( bufferID != 0 )
        {
            setActiveIndexBuffer_(bufferID);
            OGLAPI::glBufferData(GL_ELEMENT_ARRAY_BUFFER, _indiciesNum * sizeof(uint16), _initData, GL_STATIC_DRAW);
            oglIndexBuffer = new OGLIndexBuffer(bufferID, _usage, _indiciesNum);
        }
        return oglIndexBuffer;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLBufferManager::releaseIndexBuffer( IIndexBuffer* _indexBuffer )
    {
        if( m_currentIndexBuffer == _indexBuffer )
        {
            setIndexBuffer(NULL);
        }
        OGLIndexBuffer* oglIndexBuffer = static_cast<OGLIndexBuffer*>(_indexBuffer);
        if( oglIndexBuffer != NULL )
        {
            OGLAPI::glDeleteBuffers(1, &oglIndexBuffer->bufferID);
            delete oglIndexBuffer;
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    uint16* OGLBufferManager::lockIndexBuffer( IIndexBuffer* _indexBuffer, uint32 _offset, uint32 _size )
    {
        uint16* lockedMemory = NULL;
        OGLIndexBuffer* oglIndexBuffer = static_cast<OGLIndexBuffer*>(_indexBuffer);
        if( oglIndexBuffer != NULL )
        {
            setActiveIndexBuffer_(oglIndexBuffer->bufferID);
            if( oglIndexBuffer->usage == EResourceUsage::Default )
            {
                lockedMemory = static_cast<uint16*>(OGLAPI::glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY));
            }
        }
        return lockedMemory;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLBufferManager::unlockIndexBuffer( IIndexBuffer* _indexBuffer )
    {
        OGLIndexBuffer* oglIndexBuffer = static_cast<OGLIndexBuffer*>(_indexBuffer);
        if( oglIndexBuffer != NULL )
        {
            setActiveIndexBuffer_(oglIndexBuffer->bufferID);
            OGLAPI::glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLBufferManager::setIndexBuffer( IIndexBuffer* _indexBuffer )
    {
        m_currentIndexBuffer = _indexBuffer;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLBufferManager::drawPrimitive( EPrimitiveType _type, uint32 _startVertex, uint32 _primitiveCount )
    {
        OGLVertexBuffer* oglVertexBuffer = static_cast<OGLVertexBuffer*>(m_currentVertexBuffer);
        GLuint vertexBufferID = oglVertexBuffer != NULL ? oglVertexBuffer->bufferID : 0;
        setActiveVertexBuffer_(vertexBufferID);
        if( oglVertexBuffer != NULL )
        {
            applyVertexDeclaration_(oglVertexBuffer->vertexDeclaration, oglVertexBuffer->vertexSize);
        }

        OGLAPI::glDrawArrays(s_toGLPrimitiveMode[_type.raw_value()], _startVertex, _primitiveCount);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLBufferManager::drawIndexedPrimitive( EPrimitiveType _type, uint32 _baseVertexIndex, uint32 _minIndex
                                 , uint32 _verticesNum, uint32 _startIndex, uint32 _primitiveCount )
    {
        OGLVertexBuffer* oglVertexBuffer = static_cast<OGLVertexBuffer*>(m_currentVertexBuffer);
        GLuint vertexBufferID = oglVertexBuffer != NULL ? oglVertexBuffer->bufferID : 0;
        setActiveVertexBuffer_(vertexBufferID);
        if( oglVertexBuffer != NULL )
        {
            applyVertexDeclaration_(oglVertexBuffer->vertexDeclaration, oglVertexBuffer->vertexSize);
        }
        OGLIndexBuffer* oglIndexBuffer = static_cast<OGLIndexBuffer*>(m_currentIndexBuffer);
        GLuint indexBufferID = oglIndexBuffer != NULL ? oglIndexBuffer->bufferID : 0;
        setActiveIndexBuffer_(indexBufferID);
        
        if( OGLAPI::glDrawRangeElements != NULL )
        {
            OGLAPI::glDrawRangeElements(s_toGLPrimitiveMode[_type.raw_value()], _minIndex, _minIndex + _verticesNum,
            s_getIndiciesCountFromPrimitive(_type, _primitiveCount), GL_UNSIGNED_SHORT, reinterpret_cast<const GLvoid *>(_startIndex * sizeof(uint16)));
        }
        else
        {
            OGLAPI::glDrawElements(s_toGLPrimitiveMode[_type.raw_value()], s_getIndiciesCountFromPrimitive(_type, _primitiveCount)
                                 , GL_UNSIGNED_SHORT, reinterpret_cast<const GLvoid *>(_startIndex * sizeof(uint16)));
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLBufferManager::setActiveVertexBuffer_( GLuint _bufferID )
    {
        if( m_activeVertexBuffer != _bufferID )
        {
            OGLAPI::glBindBuffer(GL_ARRAY_BUFFER, _bufferID);
            m_activeVertexBuffer = _bufferID;
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLBufferManager::setActiveIndexBuffer_( GLuint _bufferID )
    {
        if( m_activeIndexBuffer != _bufferID )
        {
            OGLAPI::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _bufferID);
            m_activeIndexBuffer = _bufferID;
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void OGLBufferManager::applyVertexDeclaration_( const std::vector<VertexElement>& _vertexElements, uint32 _vertexSize )
    {
        for( std::vector<VertexElement>::const_iterator it = _vertexElements.begin(), it_end = _vertexElements.end();
                it != it_end; ++it )
        {
            const VertexElement& element = (*it);
            switch( element.semantic )
            {
                case EVertexElementSemantic::Position:
                {
                    GLenum glType;
                    GLint glSize;
                    if( element.type >= EVertexElementType::Float1 && element.type <= EVertexElementType::Float4 )
                    {
                        glType = GL_FLOAT;
                        glSize = element.type - EVertexElementType::Float1 + 1;
                    }
                    //else if
                    OGLAPI::glEnableClientState(GL_VERTEX_ARRAY);
                    OGLAPI::glVertexPointer(glSize, glType, _vertexSize, reinterpret_cast<const GLvoid *>(element.offset));
                }
                break;
                case EVertexElementSemantic::Normal:
                    OGLAPI::glEnableClientState(GL_NORMAL_ARRAY);
                    OGLAPI::glNormalPointer(GL_FLOAT, _vertexSize, reinterpret_cast<const GLvoid *>(element.offset));
                    break;
                case EVertexElementSemantic::Color:
                    OGLAPI::glEnableClientState(GL_COLOR_ARRAY);
                    OGLAPI::glColorPointer(4, GL_UNSIGNED_BYTE, _vertexSize, reinterpret_cast<const GLvoid *>(element.offset));
                    break;
                case EVertexElementSemantic::TexCoord:
                    OGLAPI::glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                    OGLAPI::glTexCoordPointer(2, GL_FLOAT, _vertexSize, reinterpret_cast<const GLvoid *>(element.offset));
                    break;
            }
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
}   // namespace nane
